上次透過簡單首頁、分頁製作了第一個rails專案,了解到其中MVC的運作原理及routes與controller之間的關聯性,這次我們將延續上次的內容添加一些功能,並透過CRUD 中(Create、 Read、 Update、 Delete)C跟R的流程來建立專案,U和D的部分就暫時有機會的時候再說明了,現在就來做做看吧!
在new.html.erb檔案中,我們需要一個表單讓使用者輸入,輸入後把資料丟到我們想要去的地方,先來建立表單吧!
<h1>新增候選人</h1>
<form action="/candidates" method="post">
<input type="text" name="username">
<input type="submit">
</form>
<%= link_to '回首頁', candidates_path, class: 'btn btn-primary btn-lg'%>
其中我們可以看到第3行的action指的是我們要將資料丟去哪裡,method則是丟出去的方法。
(在終端機執行rails routes)
使用者填完表單並按送出後,會將資料丟去
candidates POST / candidates(.:format) candidates#create
:
對candidates(.:format)
這個地方丟資料去candidates#create
的時候,需要用 post
方法。
此例我們要丟去的位置是create
,且要用post
的方式丟出,所以可以看到第3行 action="/candidate" method="post"
的部分才要用post
而不是get
。
此時的畫面長這樣:
這時候如果我們隨便輸入東西並按下提交,會出現下圖:
還記得上次的步驟嗎?
我們在controller根本還沒有給他方法,那它缺什麼,我們就給他什麼吧,回到 candidates_controller.rb
接下來應該大功告成了吧~至少應該可以按下提交後不會跳出錯誤了(灑花
人生就是有無限的BUT….
照上次的經驗來看,應該是只要新增方法就至少不會跳出錯誤了,但這裡為何會出錯呢?
原來是在rails裡面為了保護資料庫的安全,有個預設防護的機至給擋下來了。
什麼是Token?
生活情境:
小菜走在士林夜市,買了一杯位在"門口"的60嵐的珍奶並拿了20號號碼牌,想說要等,就先進去走走晃晃,看到前方剛好有一家位在"轉角"的60嵐!!!天真的小菜異想天開,想說反正都是60嵐直接拿號碼牌去領也可以吧!? 好懶得走回去喔…
跳到20號了!!!
店員: 呃(os:又來個知日 阝章)….你這個號碼牌不是我們店的喔,所以不能給你珍奶。
小菜難過的走在路上….就被端走了
token有點像在做這件事,你手上的號碼牌顯示的店面,必須要跟領取的店面相同,才能夠拿到珍奶(我們要的結果)。
<h1>新增候選人</h1>
<form action="/candidates" method="post">
<input type="text" name="username">
<input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>">
<input type="submit">
</form>
<%= link_to '回首頁', candidates_path, class: 'btn btn-primary btn-lg'%>
我們在 new.html.erb
檔案新增第5行,到開發者工具來檢視一下token:
紅色框線value的部分就是token的值,且每次重新整理都會更新一次。
這樣一來,按下提交後就不會噴錯誤訊息囉,可是不覺得每次要做個送出資料的欄位就要打這一串很麻煩嗎?
可以用上次介紹的 form_for
就可以省略複雜的那一行了,且rails會根據你在views的哪個檔案,自動幫你在button( <%= form.submit %>
)中加入文字。(ex: Updated Candidate, Edit Candidate…)
<%= form_for(物件) do |form| %>
資料打在這裡
<% end %>
Rails有個方式可以使 form_for
表單綁在Model上,在終端機輸入:
rails generate model Candidate name:string party:string age:integer policy:text
or 簡單一點的寫法 (預設就是string)
rails g model Candidate name party age:integer policy:text
新增了兩個檔案:
models下的candidates.rb
及 db>migrate>*********_create_candidates.rb
其中在*********_create_candidates.rb
會建立name(型態:string)、party(型態:string)、age(型態:integer)、policy(型態:text),後面的timestamp(created_at, updated_at)其實是內建的,用來紀錄提交時的時間點。
(備註: Rails在資料庫建立資料表的時候,會帶一個預設的隱藏ID的欄位,且在上面不會顯示,如果不想要ID欄位的話可以再下指令去除。)
到了這個步驟,其實並不是真正的建立表單了,我們只是告訴它,我有這個要建立表單的"計畫",所以還需要在終端機輸入:
rails db:migrate
才是告知"請按照這個計畫執行"。
如果此時再 rails db:migrate一次,並不會發生任何事,因為已經建立好了就辦法再更改,如果要更改已建立的檔案只有兩種方式:
建立一個新的表單並"存檔"後,再rails db:migrate一次。
rails db:rollback, 但請盡量少用,除非非常確定,否則用了rollback後沒辦法再回復。
之後再開schema.rb就可以確認是否是你要的結果了。
接著在 new.html.erb 透過 form_for將表單完成後如下:
<h1>新增候選人</h1>
<%= form_for(Candidate.new) do |form| %>
<div class="box">
<div class="candidate_field">
<%= form.label :name, '姓名' %>
<%= form.text_field :name %>
</div>
<div class="candidate_field">
<%= form.label :party, '黨派' %>
<%= form.text_field :party %>
</div>
<div class="candidate_field">
<%= form.label :age, '年紀' %>
<%= form.text_field :age %>
</div>
<div class="candidate_field">
<%= form.label :policy, '政策' %>
<%= form.text_area :policy %>
</div>
<%= form.submit %>
</div>
<% end %>
<%= link_to '回首頁', candidates_path, class: 'btn btn-primary btn-lg'%>
其中的 Candidate.new
是從Models裡的 candidate.rb
來的,但html.erb檔盡量用來做"印出內容"的事情就好,故我們不會希望在這裡用Candidate.new
來產生方法。
在 candidates_controller.rb
裡的new method建立一個實體變數 @candidate
,接著寫入至 new.html.erb
裡面取代原來的Candidate.new
。
此時按下create並不會產生任何事情,因為在controller裡的create method沒有寫任何東西,寫入下列程式至create
:
class CandidatesController < ApplicationController
def index
end
def new
@candidate = Candidate.new
end
def create
render html: params
end
end
來看看 html: params
印出的內容
{"utf8"=>"✓", "authenticity_token"=>"0OHbZ7WkDs8NsrNHK2s7Dd9BxkITn/qegeFtUcMmE5ANKKiaxmRCOiPuChEAoKh4ho/taDYBB7GvIhTG0a1IHw==", "candidate"=>{"name"=>"Louis", "party"=>"none", "age"=>"20", "policy"=>"Rails is Good"}, "commit"=>"Create Candidate", "controller"=>"candidates", "action"=>"create"}
剛剛輸入的資料就這樣全部已hash的方式印出來了,假設我們今天要取”name”裡面的value,我們可以這樣做:
render html:params["candidate"]
印出 {"name"=>"Louis", "party"=>"", "age"=>"", "policy"=>""}
render html:params["candidate"]["name"]
印出 Louis
create
的方法中可以用@candidate.save 來儲存使用者輸入的資料。
class CandidatesController < ApplicationController
def index
end
def new
@candidate = Candidate.new
end
def create
#params["candidate"]印出的是{"name"=>"Louis", "party"=>"", "age"=>"", "policy"=>""}
@candidate = Candidate.new(params["candidate"])
if @candidate.save
render html: 'ok'
else
#render html: 'error'
end
end
end
如果儲存成功,印出ok,因為還沒寫入false的條件,故先暫時不管。
輸入資料按下create之後,就出現錯誤了…..
為了避免有心人士在填資料時,偷塞參數或惡搞資料庫,造成安全問題,所以在使用者輸入資料要進資料庫的同時,利用permit來過濾並設定那些資料是可以進資料庫哪些不行。
class CandidatesController < ApplicationController
def index
end
def new
@candidate = Candidate.new
end
def create
clean_params = params.require(:candidate).permit(:name, :age, :policy, :party)
@candidate = Candidate.new(clean_params)
if @candidate.save
render html: 'ok'
else
render html: 'error'
end
end
end
clean_params = params.require(:candidate).permit(:name, :age, :policy, :party)
params.require(:candidate) => 就是找到"candidate"=>{"name"=>"Louis", "party"=>"none", "age"=>"20", "policy"=>"Rails is Good"}, "commit"=>"Create Candidate", "controller"=>"candidates", "action"=>"create"}
.permit(:name, :age, :policy, :party) => 允許:name, :age, :policy, :party 輸入的參數可以進入資料庫
就可以正常執行了。(印出ok)
礙於篇幅....好像只能先講到這,我們明天再繼續了。
參考資料:
“Flaming enthusiasm, backed up by horse sense and persistence, is the quality that most frequently makes for success.”
— Dale Carnegie, Motivational Expert
本文同步發佈於: https://louiswuyj.tw/